package timewheel

import (
	"container/list"
	"errors"
	"log/slog"
	"math"
	"sync"
	"time"
)

/**
 * @Author : bothinli
 * @Description:
 */

type TimeWheel struct {
	interval   time.Duration
	ticker     *time.Ticker
	slots      []*list.List
	currentPos int
	size       int
	exitChan   chan bool
	lock       sync.Mutex
	taskMap    map[string]*list.Element
	logger     *slog.Logger
}

type task struct {
	key      string
	funcName func(...interface{})
	params   []interface{}
	cycle    int
	index    int
}

func NewTimeWheel(opts ...TimeWheelOption) (*TimeWheel, error) {
	var timeWheel = &TimeWheel{
		interval: 1,
		size:     1024,
		exitChan: make(chan bool),
		lock:     sync.Mutex{},
		logger:   slog.Default(),
	}

	for _, opt := range opts {
		opt(timeWheel)
	}

	timeWheel.ticker = time.NewTicker(timeWheel.interval * time.Second)
	timeWheel.slots = make([]*list.List, timeWheel.size)
	for i := 0; i < timeWheel.size; i++ {
		timeWheel.slots[i] = list.New()
	}
	timeWheel.taskMap = make(map[string]*list.Element)

	return timeWheel, nil
}

func (tw *TimeWheel) AddTask(key string, fun func(...interface{}), params []interface{}, startTime time.Time) error {
	tw.logger.Info("add task", slog.String("key", key), slog.Any("startTime", startTime))

	tw.lock.Lock()
	defer tw.lock.Unlock()

	if _, ok := tw.taskMap[key]; ok {
		return errors.New("task already exist")
	}

	cycle := math.Floor((startTime.Sub(time.Now()).Seconds()/float64(tw.interval) + float64(tw.currentPos)) / float64(tw.size))
	if cycle < 0 {
		return errors.New("startTime is not valid")
	}

	index := (int(math.Ceil(startTime.Sub(time.Now()).Seconds()/float64(tw.interval))) + tw.currentPos) % tw.size
	t := &task{
		key:      key,
		funcName: fun,
		params:   params,
		cycle:    int(cycle),
		index:    index,
	}

	tw.taskMap[key] = tw.slots[index].PushBack(t)
	return nil
}

func (tw *TimeWheel) RemoveTask(key string) error {
	tw.lock.Lock()
	defer tw.lock.Unlock()
	tw.removeTask(key)
	return nil
}

func (tw *TimeWheel) Stop() error {
	tw.exitChan <- true
	return nil
}

func (tw *TimeWheel) Start() error {
	for {
		select {
		case <-tw.ticker.C:
			tw.currentPos = (tw.currentPos + 1) % tw.size
			go tw.checkTask(tw.currentPos)

		case <-tw.exitChan:
			tw.ticker.Stop()
			return nil
		}
	}
}

func (tw *TimeWheel) checkTask(index int) {
	tw.lock.Lock()

	defer tw.lock.Unlock()

	l := tw.slots[index]

	for e := l.Front(); e != nil; {
		task := e.Value.(*task)
		if task.cycle > 0 {
			task.cycle--
			e = e.Next()
			continue
		}

		tw.logger.Info("run task", slog.String("key", task.key), slog.Any("index", index))
		go task.funcName(task.params...)
		next := e.Next()
		tw.removeTask(task.key)
		e = next
	}
}

func (tw *TimeWheel) removeTask(key string) (t *task) {
	e, ok := tw.taskMap[key]
	if !ok {
		return
	}

	t = e.Value.(*task)
	delete(tw.taskMap, key)
	tw.slots[t.index].Remove(e)

	return
}
